<!--
1. 准备好媒体流的一方发送 __offer 这点很重要
-->
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>客户端</title>
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.9.1/vconsole.min.js"></script>
    <script>
        new VConsole();
        /**
         * 浏览器关闭事件
         */
        window.onbeforeunload = () => {
            console.log("离开了")
            $.ajax({
                async: false,
                type: "POST",
                url: "/exit",
                dataType: 'json',
                success: function (data) {
                    console.log(data)
                }
            });
        }
    </script>
    <style>

        .video_container {
            margin: 20px;
            height: 444px;

        }

        .video_container_one {
            background-color: antiquewhite;
            height: 100%;
            width: 600px;
            text-align: center;
            line-height: 444px;
        }
    </style>
</head>
<body>
<div id="video_area">
    <!--自己得视频 自己的视频静音-->
    <div>
        <table style="width: 100%;margin: 15px">
            <tr>
                <td class="video_container" id="seat_1">
                    <div class="video_container_one" onclick="joinVideoConference('seat_1')">加入群聊</div>
                </td>
                <td class="video_container" id="seat_2">
                    <div class="video_container_one" onclick="joinVideoConference('seat_2')">加入群聊</div>
                </td>
                <td class="video_container" id="seat_3">
                    <div class="video_container_one" onclick="joinVideoConference('seat_3')">加入群聊</div>
                </td>
            </tr>
            <tr>
                <td class="video_container" id="seat_4">
                    <div class="video_container_one" onclick="joinVideoConference('seat_4')">加入群聊</div>
                </td>
                <td class="video_container" id="seat_5">
                    <div class="video_container_one" onclick="joinVideoConference('seat_5')">加入群聊</div>
                </td>
                <td class="video_container" id="seat_6">
                    <div class="video_container_one" onclick="joinVideoConference('seat_6')">加入群聊</div>
                </td>
            </tr>
            <tr>
                <td class="video_container" id="seat_7">
                    <div class="video_container_one" onclick="joinVideoConference('seat_7')">加入群聊</div>
                </td>
                <td class="video_container" id="seat_8">
                    <div class="video_container_one" onclick="joinVideoConference('seat_8')">加入群聊</div>
                </td>
                <td class="video_container" id="seat_9">
                    <div class="video_container_one" onclick="joinVideoConference('seat_9')">加入群聊</div>
                </td>
            </tr>
        </table>
    </div>

</div>
</body>

<script type="text/javascript">

    //使用Google的stun服务器
    const iceServer = {
        "iceServers": [{
            "urls": "stun:stun.l.google.com:19302"
        }, {
            "urls": "turn:numb.viagenie.ca",
            "username": "webrtc@live.com",
            "credential": "muazkh"
        }]
    };

    //兼容浏览器的PeerConnection写法
    const PeerConnection = (window.PeerConnection ||
        window.webkitPeerConnection00 ||
        window.webkitRTCPeerConnection ||
        window.RTCPeerConnection ||
        window.mozRTCPeerConnection);

    /**
     * 所有的已建立的对等连接
     * {userId: pc}
     */
    let peerConnectionAll = {}


    /**
     * 本地视频流
     */
    let localAudioAndVideo = null;

    window.onload = async function () {
        // 进入即登录
        const user = login();
        if (!user) {
            alert("登录失败")
            return
        }
        // 定时查询消息
        setInterval(() => {
            // 每秒执行一次
            const result = getMessage();
            if (result) {
                if (result.data) {
                    console.log("收到消息：" + JSON.stringify(result));
                    if (result.data == 'requestOffer') {
                        // 请求offer事件
                        if (localAudioAndVideo) {
                            sendOffer(result.sender)
                        }
                    } else if (result.data.event === "__offer") {
                        offerHandle(result);
                    } else if (result.data.event === "__answer") {
                        answerHandle(result)
                    } else if (result.data.event === "__ice_candidate") {
                        const pc = peerConnectionAll[result.sender]
                        pc.addIceCandidate(new RTCIceCandidate(result.data.candidate));
                    }
                }
            }

        }, 1000)
    }

    function login() {
        let user = null;
        $.ajax({
            type: "POST",
            async: false,
            contentType: "application/json",
            url: "/login",
            data: JSON.stringify({}),
            dataType: "json",
            success: function (data) {
                // 登录成功
                localStorage.setItem("user", JSON.stringify(data))
                user = data
            }
        });
        return user;
    }

    /**
     * 请求跟所有的客户端建立对等连接
     */
    function getOnlineUser() {
        // 获取所有的在线用户
        let userList = []
        $.ajax({
            type: "GET",
            async: false,
            url: "/getOnlineUser",
            dataType: "json",
            success: function (data) {
                userList = data;
            }
        });
        return userList;
    }

    /**
     * 创建对等连接
     */
    function establishPC(userId) {
        // 创建对等连接
        const pc = new PeerConnection(iceServer);

        pc.onicecandidate = function (event) {
            console.log("触发onicecandidate事件")
            if (event.candidate !== null && event.candidate !== undefined && event.candidate !== '') {
                const messageBody = {
                    "recipient": userId,
                    "data": {
                        "event": "__ice_candidate",
                        "candidate": event.candidate
                    },
                }
                console.log("发送__ice_candidate消息：" + JSON.stringify(messageBody))
                sendMessage(messageBody)
            }
        };

        //如果检测到媒体流连接到本地，将其绑定到一个video标签上输出
        pc.ontrack = function (event) {
            console.log("有流过来了")
            const targetUser = getUser(userId);
            if (event.track.kind == 'audio') {
                if (event.streams && event.streams[0]) {
                    if (!document.getElementById(targetUser.userId)) {
                        const video = document.createElement("video");
                        video.autoplay = true;
                        video.controls = true;
                        video.setAttribute('webkit-playsinline', 'webkit-playsinline');
                        video.id = targetUser.userId;
                        video.classList.add('video_container_one')
                        video.srcObject = event.streams[0]
                        document.getElementById(targetUser.seatNum).innerHTML = ""
                        document.getElementById(targetUser.seatNum).appendChild(video)
                    }
                }
            } else if (event.track.kind == 'video') {
                if (event.streams && event.streams[0]) {
                    const video = document.createElement("video");
                    video.autoplay = true;
                    video.controls = true;
                    video.setAttribute('webkit-playsinline', 'webkit-playsinline');
                    video.id = targetUser.userId;
                    video.classList.add('video_container_one')
                    video.srcObject = event.streams[0]
                    document.getElementById(targetUser.seatNum).innerHTML = ""
                    document.getElementById(targetUser.seatNum).appendChild(video)
                }
            }
        };

        pc.oniceconnectionstatechange = function (event) {

            // 如果对等连接断开
            if (pc.iceConnectionState === "failed" ||
                pc.iceConnectionState === "disconnected" ||
                pc.iceConnectionState === "closed") {
                const targetUser = getUser(userId);
                if (peerConnectionAll[userId]) {
                    delete peerConnectionAll[userId]
                }
                if (targetUser) {
                    // 关闭播放组件
                    document.getElementById(targetUser.seatNum).innerHTML = ""
                    const div = document.createElement("div");
                    div.classList.add('video_container_one')
                    div.innerHTML = '加入群聊'
                    div.onclick = () => {
                        joinVideoConference(targetUser.seatNum)
                    }
                    document.getElementById(targetUser.seatNum).appendChild(div)
                }
            }
        };
        peerConnectionAll[userId] = pc;
        return peerConnectionAll[userId];

    }

    function sendMessage(data) {
        let on = false;
        $.ajax({
            async: false,
            type: "POST",
            url: "/sendMessage",
            contentType: "application/json",
            data: JSON.stringify(data),
            dataType: "json",
            success: function (data) {
                console.log(data)
                on = true;
            }
        });
        return on;
    }

    function getUser(userId) {
        let user = null;
        $.ajax({
            async: false,
            type: "GET",
            data: {userId: userId},
            url: "/getUser",
            dataType: 'json',
            success: function (data) {
                user = data;
            }
        });
        return user;
    }

    function getMessage() {
        let result = null;
        $.ajax({
            async: false,
            type: "GET",
            url: "/getMessage",
            dataType: 'json',
            success: function (data) {
                result = data;
            }
        });
        return result;
    }

    /**
     * offer事件处理
     */
    function offerHandle(data) {
        // 创建对等连接
        const pc = establishPC(data.sender);
        pc.setRemoteDescription(new RTCSessionDescription(data.data.sdp));
        // 回复应答
        pc.createAnswer().then(function (sdp) {
            pc.setLocalDescription(sdp);
            const messageBody = {
                "recipient": data.sender,
                "data": {
                    "event": "__answer",
                    "sdp": sdp
                }
            }
            console.log("发送__answer{}" + JSON.stringify(messageBody))
            sendMessage(messageBody);
        });
        if (localAudioAndVideo) {
            for (const track of localAudioAndVideo.getTracks()) {
                pc.addTrack(track, localAudioAndVideo);
            }
        }
        console.log(peerConnectionAll)
    }

    /**
     * answer 事件处理
     */
    function answerHandle(data) {
        // 创建对等连接
        const pc = peerConnectionAll[data.sender];
        pc.setRemoteDescription(new RTCSessionDescription(data.data.sdp));
        console.log("应答事件处理")
        console.log(pc)
    }

    /**
     * 加入坐席
     */
    async function joinVideoConference(seatNum) {
        // 判断是否支付获取音视频
        const json = await verifySupportability()
        if (json.video == false && json.audio == false) {
            console.log("未接入媒体设备")
            return
        } else {
            // 设置坐席
            const on = await setSeatNum(seatNum);
            if (on) {
                // 获取媒体资源
                await getLocalMedia(seatNum, json);
                if (localAudioAndVideo != null) {
                    // 如果准备好媒体资源 给所有的连接发送offer
                    // 创建对等连接
                    const userList = getOnlineUser();
                    // 创建对等连接
                    if (userList && userList.length > 0) {
                        userList.forEach(user => {
                            sendOffer(user.userId)
                        })
                    }
                } else {
                    alert("媒体资源获取失败")
                }
            }
        }

    }

    function sendOffer(userId) {
        const pc = establishPC(userId);
        // 加入推流
        console.log("开启媒体资源，像对等端推流")
        for (const track of localAudioAndVideo.getTracks()) {
            pc.addTrack(track, localAudioAndVideo);
        }
        pc.createOffer().then(sdp => {
            pc.setLocalDescription(sdp);
            const messageBody = {
                "recipient": userId,
                "data": {
                    "event": "__offer",
                    "sdp": sdp
                },
            }
            sendMessage(messageBody);
        })
        console.log(peerConnectionAll)
    }


    async function verifySupportability() {
        const json = {
            "audio": false,
            "video": false
        }
        if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
            console.log("不支持 enumerateDevices() .");
            return json;
        } else {
            if (navigator.mediaDevices.getUserMedia || navigator.webkitGetUserMedia) {
                const devices = await navigator.mediaDevices.enumerateDevices()
                /**
                 * audioinput 音频输入设备
                 * audiooutput 音频输出
                 * videoinput 视频输入
                 */
                devices.forEach(d => {
                    if (d.kind === 'audioinput') {
                        json.audio = true
                    }
                    if (d.kind === 'videoinput') {
                        json.video = true
                    }
                })
            } else {
                console.log("不支持媒体设备")
            }
        }
        return json;
    }

    /**
     * 获取本地媒体资源
     */
    async function getLocalMedia(seatNum, json) {
        if (navigator.mediaDevices.getUserMedia) {
            //最新标准API
            //获取本地的媒体流，并绑定到一个video标签上输出，并且发送这个媒体流给其他客户端
            localAudioAndVideo = await navigator.mediaDevices.getUserMedia(json);
        } else if (navigator.webkitGetUserMedia) {
            //webkit内核浏览器
            localAudioAndVideo = await navigator.webkitGetUserMedia(json);
        } else {
            alert("不支持媒体设备")
            return null;
        }
        const user = JSON.parse(localStorage.getItem("user"))
        const video = document.createElement("video");
        video.autoplay = true;
        video.muted = true;
        video.controls = true;
        video.id = user.userId;
        video.classList.add('video_container_one')
        video.srcObject = localAudioAndVideo
        document.getElementById(seatNum).innerHTML = ""
        document.getElementById(seatNum).appendChild(video)
        return localAudioAndVideo;
    }


    function setSeatNum(seatNum) {
        let result = false;
        $.ajax({
            async: false,
            type: "POST",
            url: "/setSeatNum",
            data: {seatNum: seatNum},
            dataType: 'json',
            success: function (data) {
                console.log(data)
                result = true;
            }
        });
        return result;
    }
</script>
</html>